package ru.amse.tsyganov.jumleditor.storing;

import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import ru.amse.tsyganov.jumleditor.model.ActivityGraph;
import ru.amse.tsyganov.jumleditor.model.ModelElement;
import ru.amse.tsyganov.jumleditor.model.State;
import ru.amse.tsyganov.jumleditor.model.Transition;
import ru.amse.tsyganov.jumleditor.view.ActivityGraphView;
import ru.amse.tsyganov.jumleditor.view.StateView;
import ru.amse.tsyganov.jumleditor.view.TransitionView;
import ru.amse.tsyganov.jumleditor.view.Visitor;

public class XmlWriteVisitor extends Visitor {
	
	private boolean visitTransitions;
	
	private final Document documentRoot;
	
	private final Element modelRoot;
	
	private final Element viewRoot;
	
	private final Map<ModelElement, Integer> modelToIdMap;
	
	private Element currentActivityGraphElement;
	
	private Element currentActivityGraphViewElement;
	
	private int currentId;
	
	public XmlWriteVisitor(Document root, boolean visitTransitions) {
		
		this.documentRoot = root;
		
		this.visitTransitions = visitTransitions;
		
		Element activityUmlGraphElement = documentRoot.createElement("ActivityUmlGraph");
		documentRoot.appendChild(activityUmlGraphElement);
		
		modelRoot = documentRoot.createElement("model");
		viewRoot = documentRoot.createElement("view");
		
		activityUmlGraphElement.appendChild(modelRoot);
		activityUmlGraphElement.appendChild(viewRoot);
		
		modelToIdMap = new HashMap<ModelElement, Integer>();
	}

	@Override
	public void visitActivityGraphView(ActivityGraphView<? extends ActivityGraph> g) {
		if (modelToIdMap.containsKey(g.getModel())) {
			return;
		}
		
		int id = currentId++;
		modelToIdMap.put(g.getModel(), id);
		
		currentActivityGraphElement = addNewElement("activityGraph", id, g.getModel());
		modelRoot.appendChild(currentActivityGraphElement);
		
		currentActivityGraphViewElement = addNewElement("activityGraphView", id, g);
		viewRoot.appendChild(currentActivityGraphViewElement);
	}

	@Override
	public void visitStateView(StateView<? extends State> s) {
		if (modelToIdMap.containsKey(s.getModel())) {
			return;
		}
		
		int id = currentId++;
		modelToIdMap.put(s.getModel(), id);
		
		assert currentActivityGraphElement != null;
		assert currentActivityGraphViewElement != null;
		
		Element stateElement = addNewElement("stateModel", id, s.getModel());
		currentActivityGraphElement.appendChild(stateElement);
		
		Element stateViewElement = addNewElement("stateView", id, s);
		currentActivityGraphViewElement.appendChild(stateViewElement);
	}

	@Override
	public void visitTransitionView(TransitionView<? extends Transition> t) {
		if (!visitTransitions || modelToIdMap.containsKey(t.getModel())) {
			return;
		}
		
		int id = currentId++;
		modelToIdMap.put(t.getModel(), id);
		
		assert currentActivityGraphElement != null;
		assert currentActivityGraphViewElement != null;
		
		Element transitionElement = addNewElement("transition", id, t.getModel());
		currentActivityGraphElement.appendChild(transitionElement);
		
		State source = t.getModel().getSource().getContainer();
		State target = t.getModel().getTarget().getContainer();
		
		assert source != null;
		assert target != null;
		assert modelToIdMap.containsKey(source);
		assert modelToIdMap.containsKey(target);
		
		transitionElement.setAttribute("sourceState", 
				modelToIdMap.get(source).toString());
		
		transitionElement.setAttribute("targetState", 
				modelToIdMap.get(target).toString());
		
		Element transitionViewElement = addNewElement("transitionView", id, t);
		currentActivityGraphViewElement.appendChild(transitionViewElement);
	}

	private final Element addNewElement(String name, int id, XMLStorable state) {
		Element newElement = documentRoot.createElement(name);
		newElement.setAttribute("id", Integer.toString(id));
		
		Element stateElement = documentRoot.createElement("state");
		newElement.appendChild(stateElement);
		
		stateElement.setAttribute("class", state.getClass().getName());
		state.writeToXml(stateElement);
		
		return newElement;
	}

	public boolean isVisitTransitions() {
		return visitTransitions;
	}

	public void setVisitTransitions(boolean visitTransitions) {
		this.visitTransitions = visitTransitions;
	}
}
